package com.iwhalecloud.citybrain.flow.platform.manager.application.service.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.iwhalecloud.citybrain.flow.platform.manager.common.client.HttpCommandDto;
import com.iwhalecloud.citybrain.flow.platform.manager.common.client.HttpCommandResultDto;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.log.CommonOperateLogContentVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.log.FlowCommonOperateLogVo;
import com.iwhalecloud.citybrain.flow.platform.manager.application.service.CommandHandleManager;
import com.iwhalecloud.citybrain.flow.platform.manager.application.service.CommandHandler;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FlowCommonOperateLogRepository;
import com.iwhalecloud.citybrain.flow.platform.manager.application.common.threadlocal.CommonOperateLogHolder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author lilh
 * @date 2019-02-19 22:29
 */
@Service
public class DefaultCommandHandleManager implements CommandHandleManager, InitializingBean {

    private static final String COMMAND_CODE_KEY = "commandCode";

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private FlowCommonOperateLogRepository flowCommonOperateLogRepository;

    @Autowired
    private List<CommandHandler> commandHandlers = new ArrayList<>();

    private Map<String, CommandHandler> commandToHandlerMap = new HashMap<>();

    @Override
    public HttpCommandResultDto handle(String params) {

        return doHandle((result) -> {
            String commandCode = resolveCommandCode(params);
            initCommonOperateLog(params, commandCode);
            if (StringUtils.isEmpty(commandCode)) {
                return missCommandCodeParameter(result);
            }
            CommandHandler handler = commandToHandlerMap.get(commandCode);
            if (Objects.isNull(handler)) {
                return commandCodeIsNotDefine(commandCode, result);
            }
            result = handler.handle(params);

            return result;
        });

    }

    @Override
    public HttpCommandResultDto handle(HttpCommandDto param) {
        CommonOperateLogHolder.getContent().setRequest(param);
        CommonOperateLogHolder.getCommonOperateLog().setType(param.getCommandCode());
        return doHandle((result) -> {
            CommandHandler handler = commandToHandlerMap.get(param.getCommandCode());
            if (Objects.isNull(handler)) {
                return commandCodeIsNotDefine(param.getCommandCode(), result);
            }
            return handler.handle(param);
        });
    }

    private HttpCommandResultDto doHandle(Function<HttpCommandResultDto, HttpCommandResultDto> supplier) {
        HttpCommandResultDto result = new HttpCommandResultDto();
        CommonOperateLogHolder.setFromSdk(true);
        try {
            result = supplier.apply(result);
            return result;
        } catch (Exception e) {
            result.setDealFlag(false);
            if (Objects.nonNull(e.getCause())){
                result.setDealMsg(e.getCause().getMessage());
            }else{
                result.setDealMsg(e.getLocalizedMessage());
            }
        } finally {
            attacheResultToLog(CommonOperateLogHolder.getCommonOperateLog(), result);
            FlowCommonOperateLogVo operationLogVo = CommonOperateLogHolder.getCommonOperateLog();
            FlowCommonOperateLogVo tmpVo = new FlowCommonOperateLogVo();
            BeanUtils.copyProperties(operationLogVo, tmpVo);
            removeCommonOperateLog();
            try {
                flowCommonOperateLogRepository.insertOperateLog(tmpVo);
            } catch (Exception e) {
                logger.error("流程记录通用的操作日志失败,失败原因:{}", e.getMessage(), e);
            }
        }
        return result;
    }

    private void removeCommonOperateLog() {
        CommonOperateLogHolder.removeAll();
    }

    private void initCommonOperateLog(String params, String commandCode) {
        FlowCommonOperateLogVo log = resolveOperateLog(commandCode);
        CommonOperateLogContentVo content = resolveContent(params);
        CommonOperateLogHolder.setCommonOperateLog(log);
        CommonOperateLogHolder.setContent(content);
    }

    private HttpCommandResultDto missCommandCodeParameter(HttpCommandResultDto result) {
        resolveBaseInfoFromParams();
        result.setDealFlag(false);
        result.setDealMsg("缺少参数commandCode");
        return result;
    }

    private void resolveBaseInfoFromParams() {
        FlowCommonOperateLogVo log = CommonOperateLogHolder.getCommonOperateLog();
        CommonOperateLogContentVo content = CommonOperateLogHolder.getContent();
        if (Objects.nonNull(content.getRequest())) {
            Map<String, Object> request = JSONObject.parseObject(JSONObject.toJSONString(content.getRequest()), new TypeReference<Map<String, Object>>(){});
            log.setModelKey((String) request.get("flowPackageCode"));
            log.setProcessInstanceId((String) request.get("processInstanceId"));
            log.setType((String) request.get("commandCode"));
            log.setTaskId((String) request.get("workItemId"));
        }
    }

    private HttpCommandResultDto commandCodeIsNotDefine(String commandCode, HttpCommandResultDto result) {
        resolveBaseInfoFromParams();
        logger.warn("未找到commandCode为{}的处理器", commandCode);
        result.setDealFlag(false);
        result.setDealMsg("未找到commandCode为 " + commandCode + "的处理器");
        return result;
    }

    private CommonOperateLogContentVo resolveContent(String params) {
        CommonOperateLogContentVo content = new CommonOperateLogContentVo();
        Map<String, Object> request = JSONObject.parseObject(params, new TypeReference<Map<String, Object>>() {
        });
        content.setRequest(request);
        return content;
    }

    private void attacheResultToLog(FlowCommonOperateLogVo log, HttpCommandResultDto result) {
        log.setProcessInstanceId(result.getProcessInstanceId());
        appendMsgToLog(log, result);
    }

    private void appendMsgToLog(FlowCommonOperateLogVo log, Object appendMsg) {
        CommonOperateLogContentVo content = CommonOperateLogHolder.getContent();
        content.setResponse(appendMsg);
        log.setContent(JSONObject.toJSONString(content));
    }

    private FlowCommonOperateLogVo resolveOperateLog(String commandCode) {
        FlowCommonOperateLogVo result = new FlowCommonOperateLogVo();
        result.setType(commandCode);
        return result;
    }

    @Override
    public void afterPropertiesSet() {
        for (CommandHandler handler : commandHandlers) {
            commandToHandlerMap.put(handler.handleType(), handler);
        }
    }

    private String resolveCommandCode(String params) {
        Map<String, Object> paramsMap = JSONObject.parseObject(params, new TypeReference<Map<String, Object>>() {
        });

        Object commandCode = paramsMap.get(COMMAND_CODE_KEY);
        if (Objects.isNull(commandCode)) {
            logger.warn("未传递参数{}", COMMAND_CODE_KEY);
            return null;
        }

        return (String) commandCode;
    }
}
